package com.dianpingmedia.common.utils;

import com.google.protobuf.Message;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * HTTP请求发送(连接池)
 * @author Karl He
 * @version 1.2
 * @Date 2018-11-15
 */
public class HttpRequestUtil {


    /**是否使用连接池*/
    private static final boolean ENABLE_HTTP_POOL = true;

    /**SOCKET读取超时*/
    private static final int SOCKET_TIMEOUT = 5000;

    /**从连接池获取连接的超时时间，如果连接池里连接都被用了，且超过这个connectionRequestTimeout，会抛出超时异常*/
    private static final int CONNECT_REQUEST_TIMEOUT = 5000;

    /**连接主机超时（单位：毫秒）*/
    private static final int CONNECT_TIMEOUT = 5000;

    private static final Logger logger = LoggerFactory.getLogger(HttpRequestUtil.class);
    private static PoolingHttpClientConnectionManager poolConnManager = null;


    private static final ConcurrentHashMap<Class<?>, Method> methodCache = new ConcurrentHashMap<>();

    static {
        LayeredConnectionSocketFactory sslsf = null;
        try {
            sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                .register("https", sslsf)
                .register("http", new PlainConnectionSocketFactory())
                .build();
        poolConnManager =new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        poolConnManager.setMaxTotal(100);
        poolConnManager.setDefaultMaxPerRoute(4);
        poolConnManager.setValidateAfterInactivity(2000);
        SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(SOCKET_TIMEOUT).build();
        poolConnManager.setDefaultSocketConfig(socketConfig);

        Thread t = new Thread(new Runnable(){
            @Override
            public void run(){
                if(ENABLE_HTTP_POOL) {
                    for (; ; ) {
                        poolConnManager.closeExpiredConnections();
                        try {
                            TimeUnit.SECONDS.sleep(30);
                        } catch (Exception e) {
                        }
                    }
                }
            }});
        t.start();
    }

    public static CloseableHttpClient getHttpClient() {
        CloseableHttpClient httpClient;
        if(ENABLE_HTTP_POOL) {
            RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECT_REQUEST_TIMEOUT)
                    .setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
            httpClient = HttpClients.custom()
                    .setConnectionManager(poolConnManager).setDefaultRequestConfig(requestConfig).build();
        }
        else {
            //如果不采用连接池就是这种方式获取连接
            httpClient = HttpClients.createDefault();
        }
        return httpClient;
    }


    /**
     * 发送对象,使用此方法
     * @param url
     * @param obj
     * @param urlId url方便易识别的名称(用小写字母和连接线)
     * @return
     */
    public static String postObject(String url, Object obj, String urlId){
        return post(url,obj,null, urlId);
    }


    /**
     * POST object and receive string
     * @param url
     * @param obj
     * @param headerMap
     * @return
     */
    public static String post(String url, Object obj, Map headerMap, String urlId){
        if(urlId == null){
            urlId = "default";
        }

        CloseableHttpClient httpClient = getHttpClient();
        String result = null;
        CloseableHttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);

            if(headerMap != null)
            {
                for(Iterator it = headerMap.keySet().iterator(); it.hasNext(); )
                {
                    String key = (String)it.next();
                    String value = headerMap.get(key).toString();

                    httpPost.addHeader(key, value);
                }
            }

            if(obj != null) {
                if(obj.getClass() == String.class) {
                    StringEntity entity = new StringEntity(obj.toString(), Charset.forName("UTF-8"));
                    entity.setContentType("application/x-www-form-urlencoded");
                    entity.setContentEncoding("UTF-8");
                    httpPost.setEntity(entity);
                }
                else {//class java.util.LinkedHashMap
                    StringEntity entity = new StringEntity(MiscUtil.gson.toJson(obj), Charset.forName("UTF-8"));
                    entity.setContentType("application/json; charset=UTF-8");
                    entity.setContentEncoding("UTF-8");
                    httpPost.setEntity(entity);
                }
            }

            LogUtil.debug(logger, "Post to " + url,MiscUtil.gson.toJson(obj));

            response = httpClient.execute(httpPost);
            if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                InputStream in=response.getEntity().getContent();
                result= inputStreamToString(in);
                in.close();
            }
            else
            {
                httpPost.abort();
                httpPost = null;
            }
        }
        catch (UnsupportedEncodingException e)
        {
            LogUtil.warn(logger, "Post unsupported encoding exception", e.getMessage());
        }
        catch (UnsupportedOperationException e) {
            LogUtil.warn(logger,"Post unsupported operation exception", e.getMessage());
        } catch (IOException e) {
            LogUtil.warn(logger,"Post IO exception", e.getMessage());
        }finally {
            closeResponse(response);
        }

        LogUtil.debug(logger, "Response from " + url, result);

        if(result == null) {
//            metricsMap.incrementAndGet("request.out."+urlId+".fail");
        }
        else{
//            metricsMap.incrementAndGet("request.out."+urlId+".success");
        }

        return result;
    }


    /**
     * POST protoBuf并接收字节数组
     * @param url
     * @param message 发送实体
     * @Param clazz 返回类型
     * @param urlId url的编号或代码(简短易于辨识,字母和连接线),默认后uri后三节,斜杠修改为连接线
     * @return
     */
    public static <T> T postProtobuf(String url, Message message, Class<? extends Message> clazz, String urlId) {


        byte[] receive = postBuf(url, message.toByteArray(), "application/x-protobuf", urlId);

        if(receive == null){
            return null;
        }

        if (receive.length > 1) {
            if (receive[0] == '{' && receive[receive.length - 1] == '}') {
                LogUtil.warn(logger, "postBuf response fail", new StringBuffer(0).append(url).append(" response :").append(new String(receive)).toString());
                return null;
            }
        }


        try {
            Method method = methodCache.get(clazz);
            if (method == null) {
                method = clazz.getMethod("newBuilder");
                methodCache.put(clazz, method);
            }

            Message.Builder builder = (Message.Builder)method.invoke(clazz);

            builder.mergeFrom(receive, null);

            return (T)builder.build();
        }
        catch (Exception ex) {
            LogUtil.warn(logger, "postBuf convert fail", new StringBuffer(0).append(url).append(" response :").append(new String(receive)).toString());
        }

        return null;
    }

    /**
     * POST bytes and receive bytes (收发Protobuf)
     * @param url
     * @param bytes
     * @param contentType
     * @param urlId url的编号或代码(简短易于辨识,字母和连接线),默认后uri后三节,斜杠修改为连接线
     * @return
     */
    public static byte[] postBuf(String url, byte[] bytes, String contentType, String urlId) {
        if(urlId == null) {
            urlId = processUriId(url);
        }

        if(contentType == null){
            contentType = "application/octet-stream";
        }

        byte [] result = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(new ByteArrayEntity(bytes));
            if (contentType != null) {
                httpPost.setHeader("Content-type", contentType);
            }
            CloseableHttpResponse response = null;
            CloseableHttpClient httpClient = getHttpClient();
            try {
//                metricsMap.incrementAndGet("request.out."+urlId+".total");
                response = httpClient.execute(httpPost);
                result = inputStreamToBytes(response.getEntity().getContent());
            }
            catch(IOException e)
            {
                LogUtil.warn(logger, "post() IO exception", e.getMessage());

            } finally {
                closeResponse(response);
            }
        }
        catch (Exception e)
        {
            LogUtil.warn(logger, "post() exception", e.getMessage());
        }

        if(result == null) {
//            metricsMap.incrementAndGet("request.out."+urlId+".fail");
        }
        else{
//            metricsMap.incrementAndGet("request.out."+urlId+".success");
        }

        return result;
    }



    /**
     * POST Multipart
     * @param url
     * @param inputStream
     * @return
     */
//    public static String postMultipart(String url, String fileName, InputStream inputStream) {
//        String result = null;
//        try {
//            HttpPost post = new HttpPost(url);
//            CloseableHttpResponse response = null;
//            CloseableHttpClient client = getHttpClient();
//            try {
//                MultipartEntityBuilder builder = MultipartEntityBuilder.create();
//                builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
//                //第一个参数为 相当于 Form表单提交的file框的name值 第二个参数就是我们要发送的InputStream对象了
//                //第三个参数是文件名
//                builder.addBinaryBody("file", inputStream, ContentType.create("multipart/form-data"), fileName);
//                //构建请求参数 普通表单项
//                //StringBody stringBody = new StringBody("12",ContentType.MULTIPART_FORM_DATA);
//                //builder.addPart("id",stringBody);
//                HttpEntity entity = builder.build();
//                post.setEntity(entity);
//                //发送请求
//                response = client.execute(post);
//                if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
//                    InputStream in=response.getEntity().getContent();
//                    result= inputStreamToString(in);
//                    in.close();//EntityUtils.consume(response.getEntity());
//                }
//                else
//                {
//                    post.abort();//.releaseConnection()
//                    post = null;
//                }
//            }
//            catch(IOException e)
//            {
//                LogUtil.warn(logger, "postMultipart() IO exception", e.getMessage());
//
//            } finally {
//                closeResponse(response);
//            }
//        }
//        catch (Exception e)
//        {
//            LogUtil.warn(logger, "postMultipart() exception", e.getMessage());
//        }
//
//        return result;
//    }

    /**
     * 发送文件流Binary
     * @param url
     * @param fileName
     * @param bytes
     * @return
     */
    public static String postBinary(String url, String fileName, byte[] bytes, int size) {
        String result = null;
        try {
            HttpPost post = new HttpPost(url);
            CloseableHttpResponse response = null;
            CloseableHttpClient client = getHttpClient();
            try {
                post.setEntity(new ByteArrayEntity(bytes,0,size));
                post.setHeader("Content-type", "application/octet-stream");
                post.setHeader("FileName",fileName);
                //发送请求
                response = client.execute(post);
                if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                    InputStream in=response.getEntity().getContent();
                    result= inputStreamToString(in);
                    in.close();
                    //EntityUtils.consume(response.getEntity());
                }
                else
                {
                    post.abort();
                    post = null;
                }
            }
            catch(IOException e)
            {
                LogUtil.warn(logger, "postBinary() IO exception", e.getMessage());

            } finally {
                closeResponse(response);
            }
        }
        catch (Exception e)
        {
            LogUtil.warn(logger, "postBinary() exception", e.getMessage());
        }

        return result;
    }

    /**
     * GET请求下载文件
     * @param url
     * @return
     */
    public static ByteArrayOutputStream getAndDownload(String url, Map<String,String> headerMap) {
        ByteArrayOutputStream output = null;
        try {
            HttpGet httpGet = new HttpGet(url);
            if(headerMap != null) {
                setGetHeader(httpGet, headerMap);
            }
            CloseableHttpResponse response = null;
            CloseableHttpClient httpClient = getHttpClient();
            try {
                response = httpClient.execute(httpGet);

                HttpEntity httpEntity = response.getEntity();

                InputStream is = httpEntity.getContent();
                output = httpOutputStream(is);

                EntityUtils.consume(httpEntity);
            }
            catch(IOException e)
            {
                LogUtil.warn(logger, "getFile() IO exception", e.getMessage());

            } finally {
                closeResponse(response);
            }
        }
        catch (Exception e)
        {
            LogUtil.warn(logger, "getFile() exception", e.getMessage());
        }

        return output;
    }

    /**
     * POST请求并返回文件流
     * @param url
     * @param map
     * @return
     */
    public static ByteArrayOutputStream postAndDownload(String url,Map<String,Object> map) {
        ByteArrayOutputStream output = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            if(map != null)
            {
                StringEntity entity = new StringEntity(MiscUtil.gson.toJson(map), Charset.forName("UTF-8"));
                entity.setContentType("application/json; charset=UTF-8");
                entity.setContentEncoding("UTF-8");
                httpPost.setEntity(entity);
            }
            CloseableHttpResponse response = null;
            CloseableHttpClient httpClient = getHttpClient();
            try {
                response = httpClient.execute(httpPost);

                HttpEntity httpEntity = response.getEntity();
                InputStream is = httpEntity.getContent();

                output = httpOutputStream(is);

                EntityUtils.consume(httpEntity);
            }
            catch(IOException e)
            {
                LogUtil.warn(logger, "postAndGetFile() IO exception", e.getMessage());

            } finally {
                closeResponse(response);
            }
        }
        catch (Exception e)
        {
            LogUtil.warn(logger, "postAndGetFile() exception", e.getMessage());
        }

        return output;
    }

    /**
     * 设置http GET请求的头
     * @param httpGet
     * @param headerMap
     */
    public static void setGetHeader(HttpGet httpGet, Map<String, String> headerMap) {
        if (headerMap != null && headerMap.size() > 0) {
            Set<String> keySet = headerMap.keySet();
            for (String key : keySet) {
                httpGet.addHeader(key, headerMap.get(key));
            }
        }
    }

    /**
     * 关闭HTTP response
     * @param response
     */
    public static void closeResponse(CloseableHttpResponse response){
        if(response!=null){
            try {
                response.close();
            } catch (IOException e) {
                LogUtil.warn(logger, "closeResponse() exception", e.getMessage());
            }
        }
    }

    /**
     * 接收字节流(返回ByteArrayOutputStream)
     * @param is
     * @return
     */
    public static ByteArrayOutputStream httpOutputStream(InputStream is){
        ByteArrayOutputStream output = null;
        try {
            // 根据InputStream 下载文件
            output = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int r;
            while ((r = is.read(buffer)) > 0) {
                output.write(buffer, 0, r);
            }
        }
        catch (IOException e){
            LogUtil.warn(logger, "httpOutputStream() exception", e.getMessage());
        }

        return output;
    }

    /**
     * 接收字节流(返回Bytes)
     * @param is
     * @return
     */
    public static byte[] inputStreamToBytes(InputStream is){
        try
        {
            ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
            byte [] buff = new byte[100];
            int count;
            while((count = is.read(buff,0,100))>0)
            {
                swapStream.write(buff,0,count);
            }
            return swapStream.toByteArray();
        }
        catch (Exception e)
        {
            LogUtil.warn(logger, "inputStreamToBytes() exception", e.getMessage());
        }
        return null;
    }


    /**
     * 接收字符串
     * @param is
     * @return
     */
    public static String inputStreamToString(InputStream is) {
        StringBuilder total = new StringBuilder();

        try {
            String line;
            // Wrap a BufferedReader around the InputStream
            BufferedReader rd = new BufferedReader(new InputStreamReader(is,"UTF-8"));

            // Read response until the end
            while ((line = rd.readLine()) != null) {
                total.append(line);
            }
        } catch (IOException e) {
            LogUtil.warn(logger, "inputStreamToString() exception", e.getMessage());
        }

        // Return full string
        return total.toString();
    }

    /**
     * 把url处理成urlId
     * @param url
     * @return
     */
    public static String processUriId(String url){
        String strUrl = url;
        int pos = strUrl.indexOf("//");
        if(pos > 0){
            strUrl = strUrl.substring(pos+1);
        }

        pos = strUrl.indexOf("?");
        if(pos > 0){
            strUrl = strUrl.substring(0,pos);
        }

        String [] array = strUrl.split("/");
        int start = 0;
        if(array.length > 3){
            start = array.length - 3;
        }

        StringBuilder sb = new StringBuilder(0);
        for(;start<array.length; start++){
            sb.append(array[start]);
            if(start<array.length-1){
                sb.append("-");
            }
        }

        return sb.toString();
    }
//
//    public static void resetCounter(){
//        metricsMap.clear();
//    }
//
//    public static long getCounter(String metrics){
//        return metricsMap.get(metrics);
//    }

}

